// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <windows.h>
#include <winternl.h>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/scoped_native_library.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "base/win/scoped_handle.h"

#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

namespace base {
namespace win {

    namespace testing {
        extern "C" bool __declspec(dllexport) RunTest();
    } // namespace testing

    TEST(ScopedHandleTest, ScopedHandle)
    {
        // Any illegal error code will do. We just need to test that it is preserved
        // by ScopedHandle to avoid bug 528394.
        const DWORD magic_error = 0x12345678;

        HANDLE handle = ::CreateMutex(nullptr, false, nullptr);
        // Call SetLastError after creating the handle.
        ::SetLastError(magic_error);
        base::win::ScopedHandle handle_holder(handle);
        EXPECT_EQ(magic_error, ::GetLastError());

        // Create a new handle and then set LastError again.
        handle = ::CreateMutex(nullptr, false, nullptr);
        ::SetLastError(magic_error);
        handle_holder.Set(handle);
        EXPECT_EQ(magic_error, ::GetLastError());

        // Create a new handle and then set LastError again.
        handle = ::CreateMutex(nullptr, false, nullptr);
        base::win::ScopedHandle handle_source(handle);
        ::SetLastError(magic_error);
        handle_holder = std::move(handle_source);
        EXPECT_EQ(magic_error, ::GetLastError());
    }

    TEST(ScopedHandleTest, ActiveVerifierTrackedHasBeenClosed)
    {
        HANDLE handle = ::CreateMutex(nullptr, false, nullptr);
        ASSERT_NE(HANDLE(nullptr), handle);
        typedef NTSTATUS(WINAPI * NtCloseFunc)(HANDLE);
        NtCloseFunc ntclose = reinterpret_cast<NtCloseFunc>(
            GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtClose"));
        ASSERT_NE(nullptr, ntclose);

        ASSERT_DEATH({
            base::win::ScopedHandle handle_holder(handle);
            ntclose(handle);
            // Destructing a ScopedHandle with an illegally closed handle should fail.
        },
            "");
    }

    TEST(ScopedHandleTest, ActiveVerifierDoubleTracking)
    {
        HANDLE handle = ::CreateMutex(nullptr, false, nullptr);
        ASSERT_NE(HANDLE(nullptr), handle);

        base::win::ScopedHandle handle_holder(handle);

        ASSERT_DEATH({
            base::win::ScopedHandle handle_holder2(handle);
        },
            "");
    }

    TEST(ScopedHandleTest, ActiveVerifierWrongOwner)
    {
        HANDLE handle = ::CreateMutex(nullptr, false, nullptr);
        ASSERT_NE(HANDLE(nullptr), handle);

        base::win::ScopedHandle handle_holder(handle);
        ASSERT_DEATH({
            base::win::ScopedHandle handle_holder2;
            handle_holder2.handle_ = handle;
        },
            "");
        ASSERT_TRUE(handle_holder.IsValid());
        handle_holder.Close();
    }

    TEST(ScopedHandleTest, ActiveVerifierUntrackedHandle)
    {
        HANDLE handle = ::CreateMutex(nullptr, false, nullptr);
        ASSERT_NE(HANDLE(nullptr), handle);

        ASSERT_DEATH({
            base::win::ScopedHandle handle_holder;
            handle_holder.handle_ = handle;
        },
            "");

        ASSERT_TRUE(::CloseHandle(handle));
    }

    TEST(ScopedHandleTest, MultiProcess)
    {
        // Initializing ICU in the child process causes a scoped handle to be created
        // before the test gets a chance to test the race condition, so disable ICU
        // for the child process here.
        CommandLine command_line(base::GetMultiProcessTestChildBaseCommandLine());
        command_line.AppendSwitch(switches::kTestDoNotInitializeIcu);

        base::Process test_child_process = base::SpawnMultiProcessTestChild(
            "ActiveVerifierChildProcess", command_line, LaunchOptions());

        int rv = -1;
        ASSERT_TRUE(test_child_process.WaitForExitWithTimeout(
            TestTimeouts::action_timeout(), &rv));
        EXPECT_EQ(0, rv);
    }

    MULTIPROCESS_TEST_MAIN(ActiveVerifierChildProcess)
    {
        ScopedNativeLibrary module(FilePath(L"scoped_handle_test_dll.dll"));

        if (!module.is_valid())
            return 1;
        auto run_test_function = reinterpret_cast<decltype(&testing::RunTest)>(
            module.GetFunctionPointer("RunTest"));
        if (!run_test_function)
            return 1;
        if (!run_test_function())
            return 1;

        return 0;
    }

} // namespace win
} // namespace base
